package client;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;

import util.Logger;
import util.SerializationHelper;

import UI.UI;
import UI.UIinstaller;
import dataContracts.Command;
import dataContracts.FileStatus;
import dataContracts.InstallSettings;
import dataContracts.SharedFile;
import dataContracts.SharedDirectory;

public class ClientProgram {
	InstallSettings settings;
	Socket socket;
	ObjectOutputStream objectWriter;
	ObjectInputStream objectReader;
	private UI ui;
	private MessageHandler msgHandler;
	private static UIinstaller uis;
	private static InstallSettings s;
	
	ClientDirectory directory;
	
	public ClientProgram(InstallSettings settings) {
		this.settings = settings;
	}

	public static void main(String[] args) throws IOException {

		SerializationHelper<InstallSettings> sh = new SerializationHelper<InstallSettings>();
        File theSettingFile = new File(System.getProperty("user.home") + File.separator + "LANconfig.cfg");
        
        if(theSettingFile.exists()) {
        	s = sh.Deserialize(theSettingFile);
        }
        
        if(s != null){
    		ClientProgram program = new ClientProgram(s);

    		if (program.environmentIsConfigured()) {
    			try {
    				program.run();
    			} catch (NumberFormatException e) {
    				e.printStackTrace();
    			} catch (ClassNotFoundException e) {
    				e.printStackTrace();
    			}
    		} else {
    			Logger.Error("You're environment is not configured. Create the following directories: \n\t"+ s.directoryPath);
    		}

        } else {
        	s = new InstallSettings();
        	uis.run(s);
        }
		
	}
	
	private boolean environmentIsConfigured() {
		File folder = new File(settings.directoryPath);
		return (folder.exists() && folder.isDirectory());
	}

	public void run() throws NumberFormatException, ClassNotFoundException {
		directory = new ClientDirectory(s.directoryPath);
		directory.config = s;
		
		msgHandler = new MessageHandler(this);
		ui = new UI(msgHandler, directory);
		
		connect();
		
		// bail if the server is offline
		if (socket == null) {
			return;
		}
		
		Command cmd;
		try {
			while (true) {
				cmd = (Command)objectReader.readObject();
				// parse out the 'body' portion of the message (i.e. just remove the command type prefix)
				String commandBody = cmd.commandBody;
				
				switch (cmd.commandType) {
				case NewFile:
					// this triggers the new file download
					msgHandler.GetFile(commandBody);
					break;
				case UpdateFile:
					//TODO: add logic to ask user to confirm overwrite first?
					msgHandler.GetFile(commandBody);
					break;
				case DeleteFile:
					Logger.Debug("Delete from client: "+ commandBody);
					directory.deleteFile(commandBody);
					break;
				case UploadFile:
					msgHandler.RequestUpload(commandBody);
					break;
				case ResolveDiff:
					// get the local file reference...
					SharedFile cf = directory.findFile(commandBody);
					if (cf !=  null && cf.status == FileStatus.Added) {
						// tell the server to prepare for upload
						msgHandler.RequestUpload(commandBody);	
						Logger.Debug("Resolved: Upload "+ commandBody);
					} else {
						// if it wasn't added locally, remove it...
						directory.removeFile(commandBody);
						Logger.Debug("Resolved: Remove "+ commandBody);
					}
					
					break;
				case GetFile:
					// upload the file
					String[] args = commandBody.split(":");
					String file = args[0], port = args[1];
					DownloadClient client = new DownloadClient(file, settings.serverAddress, Integer.parseInt(port), directory);
					client.operation = "upload";
					Thread t = new Thread(client);
					t.start();
					
					msgHandler.AddLogMsg("Uploading file: "+ file);
					
					break;
				case RecvFile:
					// this kicks off a new thread which does the download...
					String[] args1 = commandBody.split(":");
					String file1 = args1[0], port1 = args1[1];
					
					Thread t1 = new Thread(new DownloadClient(file1, settings.serverAddress, Integer.parseInt(port1), directory));
					t1.start();
					
					msgHandler.AddLogMsg("Downloading: "+ file1);
					
					break;
				case Broadcast:
					// TODO: why can't I run this command through the MessageHandler like: msgHandler.AddLogMsg(message);
					msgHandler.AddLogMsg(commandBody);
					break;
					
				case MarkAsCurrent:
					// this merely ensures that any outdated flags (updated, added, etc) have been cleared
					SharedFile curr = directory.findFile(commandBody);
					if (curr != null)
						curr.status = FileStatus.Synchronized;
					
					break;
				case IsEqual:
					if (commandBody.equals("1")) { //equal
						msgHandler.AddLogMsg(("Directories are in sync"));
					} else if (commandBody.equals("0")) { //NOT equal
						msgHandler.AddLogMsg("Directories are NOT in sync");
						directory.refresh();
						// request a synch from the server
						msgHandler.RequestSynch();
					} else {
						msgHandler.AddLogMsg("There was a problem checking for differences ("+commandBody+")...");
					}
					
					break;
				case Synch:
					// nothing really happens here, since the server process kicks off the following commands
					// directly from the server-side Synch call: (see above case statements for how they're handled) 
					//  - NewFile
					// 	- DeleteFile
					//	- UpdateFile
					
					break;
				default:
					msgHandler.AddLogMsg("Unknown command from server: "+ cmd.commandType +":"+ cmd.commandBody);
					break;
				}
			}
		}  catch(EOFException eof) {
			closeConnection();
			Logger.Debug("disconnected from server");
			
		} catch (ClassCastException e) {
			Logger.Debug("WTF bad message received");
		} catch (IOException e) { e.printStackTrace(); }
		
		ui.run();
	}

	private void configureSocket() {
		try {
			// create socket, and configure read/write streams
			socket = new Socket(settings.serverAddress, settings.serverPort);
			objectReader = new ObjectInputStream(socket.getInputStream());
			objectWriter = new ObjectOutputStream(socket.getOutputStream());
			
			Logger.Debug("Connected to server... ");
		} catch (SocketException e) {
			msgHandler.AddLogMsg("Server offline. Try again later.");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void closeConnection() {
		try {
			objectWriter.close();
			objectReader.close();
			socket.close();
			socket = null;
		} catch (IOException e) {
			Logger.Warning("Client couldn't be closed correctly...");
		}
	}
	
	public boolean isConnected() {
		return socket != null && socket.isConnected();
	}
	
	public void disconnect() {
		closeConnection();
		// disconnect from the server, clean up, etc.
	}
	
	public void connect() {
		configureSocket();
//		msgHandler.registerWithServer();
//		msgHandler = new MessageHandler(this);
//		ui.reconnect(msgHandler, directory);
	}
}